package com.dream.libxrec.player.video;

import android.media.Image;
import android.media.MediaCodec;
import android.media.MediaCodecInfo;
import android.media.MediaExtractor;
import android.media.MediaFormat;
import android.os.Build;
import android.util.Log;
import android.view.Surface;

import java.nio.ByteBuffer;

public class VideoDecoder {

    private static final String TAG = "H264Decoder";
    private static final int DECODE_COLOR_FORMAT =
            MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar;
    private static final long DEFAULT_TIMEOUT_US = 10000;
    private volatile boolean mStop = true;
    private volatile boolean isRender = false;//是否渲染到Surface上
    private volatile boolean isPause;//暂停解码
    private volatile boolean isSeeking = false;
    private volatile long mSeekPts = -1L;
    private volatile boolean isOnlyDecodeIFrame = false;//是否仅解码回调关键帧
    private final Object mPauseLock = new Object();
    private final Object mEndLock = new Object();
    //读取到文件结尾等待进一步操作（退出或seek等）的锁，避免直接释放了资源
    private MediaExtractor mMediaExtractor = null;
    private MediaCodec mMediaCodec = null;
    private IVideoDecodeImageCallback mImageCB = null;
    private IVideoDecodeSurfaceCallback mSurfaceCB = null;

    private MediaFormat mMediaFormat;
    private long mDurationUs;//视频轨时长，单位为微秒
    private boolean isEndRelease = false;//读到文件结尾时自动释放资源

    public boolean init(String videoFilePath,
                        Surface surface,
                        IVideoDecodeSurfaceCallback cb) throws Exception{
        this.mSurfaceCB = cb;
        return initDecode(videoFilePath,surface);
    }

    public boolean init(String videoFilePath,
                        IVideoDecodeImageCallback mCB) throws Exception{
        this.mImageCB = mCB;
        return this.initDecode(videoFilePath,null);
    }


    private boolean initDecode(String path,Surface surface) throws Exception{
        this.mMediaExtractor = new MediaExtractor();
        this.mMediaExtractor.setDataSource(path);
        int videoTrackIndex = selectVideoTrack(mMediaExtractor);
        if (videoTrackIndex < 0) {
            Log.e(TAG, "No video track found in " + path);
            return false;
        }

        MediaFormat mediaFormat = mMediaExtractor.getTrackFormat(videoTrackIndex);
        String mime = mediaFormat.getString(MediaFormat.KEY_MIME);
        mDurationUs = mediaFormat.getLong(MediaFormat.KEY_DURATION);
        this.mMediaCodec = MediaCodec.createDecoderByType(mime);

//        int width = mediaFormat.getInteger(MediaFormat.KEY_WIDTH);
//        int height = mediaFormat.getInteger(MediaFormat.KEY_HEIGHT);
        isRender = surface != null;
        if (!isRender){
            //如果为直接渲染Surface模式，直接渲染即可，否则就需要去获取YUV数据，设置YUV数据类型
            mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, DECODE_COLOR_FORMAT);
        }

        this.mMediaFormat = mediaFormat;

        if (mImageCB != null){
            mImageCB.init(mediaFormat);
        }

        if (mSurfaceCB != null){
            mSurfaceCB.init(mediaFormat);
        }

        this.mMediaCodec.configure(mediaFormat, surface, null, 0);

        return true;
    }

    public void start() throws Exception{
        mStop = false;
        mMediaCodec.start();

        MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
        boolean sawInputEOS = false;
        boolean sawOutputEOS = false;

        while (!mStop && !sawOutputEOS) {
            if (!sawInputEOS) {
                int inputBufferId = mMediaCodec.dequeueInputBuffer(DEFAULT_TIMEOUT_US);
                if (inputBufferId >= 0) {
                    ByteBuffer inputBuffer = getMediaCodecInputBuffer(mMediaCodec, inputBufferId);
                    int sampleSize = mMediaExtractor.readSampleData(inputBuffer, 0);

                    if (sampleSize < 0) {
                        if (isEndRelease){
                            // 读取到文件末尾,通知解码器结束
                            mMediaCodec.queueInputBuffer(inputBufferId, 0, 0, 0L, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
                            sawInputEOS = true;
                            Log.i(TAG, "sawInputEOS is true");
                        }else{
                            synchronized (mEndLock){
                                try {
                                    mEndLock.wait();
                                } catch (InterruptedException e) {
                                    e.printStackTrace();
                                }
                            }
                            continue;
                        }
                    } else {
                        mMediaCodec.queueInputBuffer(inputBufferId, 0, sampleSize, mMediaExtractor.getSampleTime(), 0);
                        mMediaExtractor.advance();
                    }
                }
            }
            int outputBufferId = mMediaCodec.dequeueOutputBuffer(info, DEFAULT_TIMEOUT_US);
            if (outputBufferId >= 0) {
                if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
                    sawOutputEOS = true;
                    Log.i(TAG, "sawOutputEOS is true");
                }
                if (info.size > 0) {
                    long pts  = info.presentationTimeUs;//获取帧时间戳
                    boolean isIFrame =
                            (info.flags & MediaCodec.BUFFER_FLAG_KEY_FRAME) != 0;

                    if (isOnlyDecodeIFrame &&!isIFrame){
                        mMediaCodec.releaseOutputBuffer(outputBufferId,
                                false);
                        continue;
                    }

                    if (mImageCB != null){
                        Image image = mMediaCodec.getOutputImage(outputBufferId);

                        if (!isPause&&!isSeeking){
                            mImageCB.onImageDecode(image,pts);
                        }

                        if (image!= null){
                            image.close();
                        }
                    }else if (mSurfaceCB != null){
                        if (!isPause&&!isSeeking){
                            mSurfaceCB.onSurfaceDecode(pts);
                        }
                    }

                    mMediaCodec.releaseOutputBuffer(outputBufferId,
                            isRender);
                }
            }

            if (isPause&&!mStop){
                synchronized (mPauseLock){
                    try {
                        mPauseLock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }

            if (isSeeking && mSeekPts >= 0){
                mMediaExtractor.seekTo(mSeekPts,
                        MediaExtractor.SEEK_TO_CLOSEST_SYNC);
                mMediaCodec.flush();// 清空MediaCodec缓冲数据

                Log.i(TAG, "video seekTo: " + mSeekPts);
                isSeeking = false;
                mSeekPts = -1;
            }
        }

        release();
    }

    private ByteBuffer getMediaCodecInputBuffer(MediaCodec codec,
                                                int index){
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP){
            return codec.getInputBuffer(index);
        }else{
            ByteBuffer[] codecInputBuffers = codec.getInputBuffers();
            return codecInputBuffers[index];
        }
    }

    public void onPause(boolean isPause){
        this.isPause = isPause;
        if (!isPause){
            synchronized (mPauseLock){
                mPauseLock.notifyAll();
            }
        }
    }

    public void close(){
        if (!mStop){
            mStop = true;
            if (isPause){
                isPause = false;
                synchronized (mPauseLock){
                    mPauseLock.notifyAll();
                }
            }

            synchronized (mEndLock){
                mEndLock.notify();
            }
        }else{
            release();
        }
    }

    private void release(){
        mStop = true;

        if (mMediaCodec != null){
            try {
                mMediaCodec.release();
            } catch (Exception ignored) {}

            try {
                mMediaCodec.stop();
            } catch (Exception ignored) {}

            mMediaCodec = null;
        }

        mMediaFormat = null;

        if (mMediaExtractor != null){
            try {
                mMediaExtractor.release();
            }catch (Exception ignored){}

            mMediaExtractor = null;
        }

        Log.i(TAG,"AudioDecoder release finish");

        if (mImageCB != null){
            mImageCB.onFinish();
        }

        if (mSurfaceCB != null){
            mSurfaceCB.onFinish();
        }
    }

    public MediaFormat getFormat(){
        return mMediaFormat;
    }

    private static int selectVideoTrack(MediaExtractor mMediaExtractor) {
        int numTracks = mMediaExtractor.getTrackCount();
        for (int i = 0; i < numTracks; i++) {
            MediaFormat format = mMediaExtractor.getTrackFormat(i);
            String mime = format.getString(MediaFormat.KEY_MIME);
            if (mime!=null&&mime.startsWith("video/")) {
                mMediaExtractor.selectTrack(i);
                return i;
            }
        }
        return -1;
    }

    /**
     *
     * @param isOnlyDecodeIFrame 是否仅解码关键帧
     */
    public void setDecodeMode(boolean isOnlyDecodeIFrame) {
        this.isOnlyDecodeIFrame = isOnlyDecodeIFrame;
    }

    public long getDurationUs(){
        return mDurationUs;
    }

    public void seekTo(long pts) {
        if (mMediaExtractor == null){
            Log.i(TAG,"video media extractor is release");
            return;
        }

        isSeeking = true;
        mSeekPts = pts;

        synchronized (mEndLock){
            mEndLock.notifyAll();
        }

        synchronized (mPauseLock){
            mPauseLock.notifyAll();
        }
    }

    /**
     * 是否在文件结尾时自动释放资源（如果为true，播放结束后会自动释放，但无法再进行seek），默认为false
     * @param isEndRelease
     */
    public void isEndRelease(boolean isEndRelease){
        this.isEndRelease = isEndRelease;
    }

    public boolean isSeeking() {
        return isSeeking;
    }
}
